package com.softwin.cryptocurrency.v2demo.spread;

import com.softwin.sc.base.BaseStrategy;
import com.softwin.sc.base.bean.common.DepthsData;
import com.softwin.sc.base.bean.common.Order;
import com.softwin.sc.base.bean.common.Symbol;
import com.softwin.sc.base.bean.enumeration.OrderStatus;
import com.softwin.sc.base.bean.req.ReqInsertOrder;
import com.softwin.sc.base.bean.rsp.AssetBalance;
import com.softwin.sc.base.bean.rsp.RspSymbol;
import com.softwin.sc.base.bean.rsp.Subaccount;
import com.softwin.sc.base.bean.rtn.RtnDepthsData;
import com.softwin.sc.base.bean.rtn.RtnOrder;
import com.softwin.sc.base.bean.rtn.RtnTrade;
import com.softwin.sc.base.exception.StrategyException;
import com.softwin.sc.base.facade.StrategySpi;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by michael tsao on 2018/08/18.
 */
@Slf4j
public class SpreadStrategy extends BaseStrategy implements StrategySpi {
    public void onDepths(RtnDepthsData rtnDepthsData){
        if (!rtnDepthsData.getCRsp().isSuccess()) {
            return;
        }
        DepthsData depthsData = rtnDepthsData.getData();
        String exchange = depthsData.getSymbol().getExchangeType();
        BidAsk1 bidAsk1 = new BidAsk1(depthsData.getSymbol().getExchangeType(), new BigDecimal(depthsData.getBids().get(0).getPrice()),
                new BigDecimal(depthsData.getBids().get(0).getQuantity()), new BigDecimal(depthsData.getAsks().get(0).getPrice()),
                new BigDecimal(depthsData.getAsks().get(0).getQuantity()), depthsData.getTimestamp());
        bidAsk1Map.put(exchange, bidAsk1);
        for (Map.Entry<String, ArbitrageSymbol> entry : arbitrageSymbolMap.entrySet()) {
            if (!bidAsk1.equals(entry.getValue().exchangABidAsk1) || !bidAsk1.equals(entry.getValue().exchangBBidAsk1)) {
                entry.getValue().update(exchange, bidAsk1);
            }

            if (entry.getKey() != null) {
                if (arbitrageName.equals(entry.getKey())) {
                    ArbitrageSymbol arbitrageSymbol = entry.getValue();
                    log.debug("Before changed:" + arbitrageSymbol.toString() + "  " + expectedPrice.toString());
                    if (arbitrageSymbol.getArbitrageAskPrice() != null && arbitrageSymbol.getArbitrageBidPrice() != null
                            && arbitrageSymbol.getArbitrageBidQuantity().compareTo(BigDecimal.ZERO) == 1 && arbitrageSymbol.getArbitrageAskQuantity().compareTo(BigDecimal.ZERO) == 1) {
                        runStrategy(arbitrageSymbol);
                    }
                }
            }
        }
    }

    public void onRtnOrder(RtnOrder rtnOrder) {
        if(rtnOrder.getCRsp().isSuccess()){
            Subaccount subaccount = getSubaccounts().get(rtnOrder.getOrder().getSubaccountCode());
            updatePositionMap(subaccount, "base", rtnOrder.getOrder().getSymbol().getBaseCurrency());
            updatePositionMap(subaccount, "quote", rtnOrder.getOrder().getSymbol().getQuoteCurrency());
        }
    }

    public void onRtnTrade(RtnTrade rtnTrade){
        if(rtnTrade.getCRsp().isSuccess()){
            Subaccount subaccount = getSubaccounts().get(rtnTrade.getTrade().getSubaccountCode());
            updatePositionMap(subaccount, "base", rtnTrade.getTrade().getSymbol().getBaseCurrency());
            updatePositionMap(subaccount, "quote", rtnTrade.getTrade().getSymbol().getQuoteCurrency());
        }
    }

    public void onInit(){
        initTG();
        initMarketData();
    }
    @Data
    private class Position {
        private String positionKey;
        private String exchange;
        private String subaccount;
        private String type;

        private String currency;
        //private String quoteCurrency;
        private BigDecimal currencyFree;
        private BigDecimal currencyLocked;

        public Position(String exchangeType, String subaccountCode, String type, AssetBalance subaccountBalance) {
            this.currency = subaccountBalance.getAsset();
            this.exchange = exchangeType;
            this.subaccount = subaccountCode;
            this.type = type;
            this.currencyFree = subaccountBalance.getFree();
            this.currencyLocked = subaccountBalance.getLocked();
        }
        //private BigDecimal quoteCurrencyFree;
        //private BigDecimal quoteCurrencyLocked;

        public String getPositionKey() {
            if (exchange != null && currency != null)
                positionKey = exchange + currency;
            return positionKey;
        }

        @Override
        public String toString() {
            return this.getPositionKey() + ",subAccount=" + this.subaccount
                    + ",CurrencyFree=" + currencyFree.setScale(5, RoundingMode.FLOOR).toString()
                    + ",CurrencyLocked=" + currencyLocked.setScale(5, RoundingMode.FLOOR).toString();
            //+ ",quoteCurrencyFree=" + quoteCurrencyFree.setScale(5, RoundingMode.HALF_EVEN).toString()
            //+ ",quoteCurrencyLocked=" + quoteCurrencyLocked.setScale(5, RoundingMode.HALF_EVEN).toString() ;
        }
    }

    @Data
    private class ExpectedOpenClosePrice {
        private String arbitrageName;
        private BigDecimal expectedBuyPrice;
        private BigDecimal expectedSellPrice;
        private BigDecimal expectedStopBuyPrice;
        private BigDecimal expectedStopSellPrice;

        @Override
        public String toString() {
            return getArbitrageName() + " : Expected: "
                    + getExpectedBuyPrice().setScale(5, RoundingMode.HALF_EVEN).toString() + "," + getExpectedSellPrice().setScale(5, RoundingMode.FLOOR).toString() + ","
                    + getExpectedStopBuyPrice().setScale(5, RoundingMode.HALF_EVEN).toString() + "," + getExpectedStopSellPrice().setScale(5, RoundingMode.FLOOR).toString();
        }
    }

    @Data
    @AllArgsConstructor
    private class BidAsk1 {
        private String exchange;
        private BigDecimal bid1Price;
        private BigDecimal bid1Quantity;
        private BigDecimal ask1Price;
        private BigDecimal ask1Quantity;
        private long timestamp;

        public boolean equals(Object obj) {
            if (obj instanceof BidAsk1) {
                BidAsk1 bidAsk1 = (BidAsk1) obj;
                return (exchange.equals(bidAsk1.exchange) && bid1Price.compareTo(bidAsk1.bid1Price) == 0
                        && bid1Quantity.compareTo(bidAsk1.bid1Quantity) == 0 && ask1Price.compareTo(bidAsk1.ask1Price) == 0
                        && ask1Quantity.compareTo(bidAsk1.ask1Quantity) == 0);
            }
            return super.equals(obj);
        }

        public BigDecimal getLastPrice() {
            if (bid1Price != null && ask1Price != null)
                return (bid1Price.add(ask1Price)).divide(new BigDecimal("2"));
            else
                return BigDecimal.ZERO;
        }

        public int hashCode() {
            return exchange.hashCode() + bid1Price.toString().hashCode() + ask1Price.toString().hashCode() +
                    bid1Quantity.toString().hashCode() + ask1Quantity.toString().hashCode();
        }
    }

    @Data
    private class Precision {
        private String exchange;
        private String baseCurrency;
        private String quoteCurrency;
        private Symbol symbol;

        private BigDecimal pricePrecision;
        private BigDecimal amountPrecision;
        private BigDecimal minAmount;

        @Override
        public String toString() {
            return exchange + ",baseCurrency=" + baseCurrency + " : quoteCurrency : " + quoteCurrency + ","
                    + getPricePrecision().setScale(5, RoundingMode.FLOOR).toString() + "," + getAmountPrecision().setScale(5, RoundingMode.FLOOR).toString() + ","
                    + getMinAmount().setScale(5, RoundingMode.FLOOR).toString();
        }
    }

    /*
    | string     | pricePrecision  | 价格精度           |
    | string     | amountPrecision | 数量精度           |
    | Symbol     | symbol          | 交易对             |
    | string     | minAmount       | 最小成交量         |
    */

    @Data
    private class ArbitrageSymbol {
        private String ExchangeA;
        private String ExchangeB;
        private String baseCurrency;
        private String quoteCurrency;
        private BidAsk1 exchangABidAsk1;
        private BidAsk1 exchangBBidAsk1;
        private String arbitrageName;
        private Precision aSymbolPrecision;
        private Precision bSymbolPrecision;
        private long updateTime;


        private StrategyStatus status;
        private StrategyStatus previousStatus;


        public ArbitrageSymbol(String exchangea, String exchangeb, String baseCurrency, String quoteCurrency) {
            this.ExchangeA = exchangea;
            this.ExchangeB = exchangeb;
            this.baseCurrency = baseCurrency;
            this.quoteCurrency = quoteCurrency;
            this.status = StrategyStatus.NoN;
            this.previousStatus = StrategyStatus.NoN;

            this.updateTime = 0;
        }

        public String getArbitrageName() {
            arbitrageName = getExchangeA() + "-" + getExchangeB() + ":" + this.getBaseCurrency() + "/" + this.getQuoteCurrency();
            return arbitrageName;
        }

        public BigDecimal getArbitrageBidPrice() {
            if (exchangABidAsk1 != null && exchangBBidAsk1 != null) {
                return exchangABidAsk1.getBid1Price().subtract(exchangBBidAsk1.getAsk1Price());
            }
            return BigDecimal.ZERO;
        }

        public BigDecimal getArbitrageAskPrice() {
            if (exchangABidAsk1 != null && exchangBBidAsk1 != null) {
                return exchangABidAsk1.getAsk1Price().subtract(exchangBBidAsk1.getBid1Price());
            }

            return BigDecimal.ZERO;
        }


        public BigDecimal getArbitrageBidQuantity() {
            if (exchangABidAsk1 != null && exchangBBidAsk1 != null) {
                return exchangABidAsk1.getBid1Quantity().setScale(5, RoundingMode.HALF_EVEN).min(exchangBBidAsk1.getAsk1Quantity().setScale(5, RoundingMode.FLOOR));
            }
            return BigDecimal.ZERO;
        }

        public BigDecimal getArbitrageAskQuantity() {
            if (exchangABidAsk1 != null && exchangBBidAsk1 != null) {
                return exchangABidAsk1.getAsk1Quantity().setScale(5, RoundingMode.HALF_EVEN).min(exchangBBidAsk1.getBid1Quantity().setScale(5, RoundingMode.FLOOR));
            }
            return BigDecimal.ZERO;
        }


        public void update(String exchange, BidAsk1 bidAsk1) {
            if (ExchangeA.equals(exchange))
                exchangABidAsk1 = bidAsk1;
            else if (ExchangeB.equals(exchange))
                exchangBBidAsk1 = bidAsk1;
            if (exchangABidAsk1 != null && exchangBBidAsk1 != null) {
                if (exchangABidAsk1.getTimestamp() > updateTime) {
                    updateTime = exchangABidAsk1.getTimestamp();
                }
                if (exchangBBidAsk1.getTimestamp() > updateTime) {
                    updateTime = exchangBBidAsk1.getTimestamp();
                }
            }

        }

        @Override
        public String toString() {
            return getArbitrageName() + ",status=" + status + ",previousStatus=" + previousStatus + ",updateTime=" + updateTime
                    + " : bid,bidQ,ask,askQ : " +
                    getArbitrageBidPrice().setScale(5, RoundingMode.FLOOR).toString() + "," + getArbitrageBidQuantity().setScale(5, RoundingMode.FLOOR).toString() + ","
                    + getArbitrageAskPrice().setScale(5, RoundingMode.FLOOR).toString() + "," + getArbitrageAskQuantity().setScale(5, RoundingMode.FLOOR).toString() + ","
                    ;
        }
    }

    public enum PositionStatus {
        READY, NOPOSITION, LONGPOSITION, LONGFULLPOSITION, BUYOPEN, SELLCLOSE
    }

    public enum StrategyStatus {
        NoN, Buy, StopBuy, Sell, StopSell
    }

    private String baseCurrency = "eth";
    private String quoteCurrency = "usdt";

    private BigDecimal maxTradeQuantity = new BigDecimal("0.3"); //下单量
    private BigDecimal rangeDelta = new BigDecimal("2"); // 套利策略运行的空间
    private BigDecimal openRange = new BigDecimal("0.2"); // ask1 与 bid1的差距达到一个gap后才触发交易
    private BigDecimal basePrice = new BigDecimal("-0.1"); // 开仓基准价
    private BigDecimal gDelta = new BigDecimal("0.0");
    private Map<String, Position> positionMap = new HashMap<>(); //全部的Position,key为exchange，value为postion

    private List<String> exchanges = Arrays.asList("binance", "huobi"); // 交易所

    private Map<String, Symbol> symbols = new HashMap<>(); //交易币对
    private Map<String, BidAsk1> bidAsk1Map = new HashMap<>(); //最新bid1ask1
    private Map<String, ArbitrageSymbol> arbitrageSymbolMap = new HashMap<>(); //最新arbitrage symbol

    public ExpectedOpenClosePrice expectedPrice = new ExpectedOpenClosePrice();
    public String arbitrageName = exchanges.get(0) + "-" + exchanges.get(1) + ":" + baseCurrency + "/" + quoteCurrency;

    public static void main(String[] args) throws InterruptedException {
        SpreadStrategy ta = new SpreadStrategy();
        ta.start("15618929530", "147258", ta);
        while (true) {
            Thread.sleep(3000);
        }
    }

    private void initTG() {
        for (String exchange : exchanges) {
            Symbol symbol = Symbol.builder().baseCurrency(baseCurrency).quoteCurrency(quoteCurrency).exchangeType(exchange).build();
            symbols.putIfAbsent(exchange, symbol);
        }

        for (String exchange : exchanges) {
            bidAsk1Map.put(exchange, null);
        }

        for (int i = 0; i < exchanges.size(); i++) {
            for (int j = i + 1; j < exchanges.size(); j++) {
                ArbitrageSymbol symbol = new ArbitrageSymbol(exchanges.get(i), exchanges.get(j), baseCurrency, quoteCurrency);
                String exchangePair = symbol.getArbitrageName();
                arbitrageSymbolMap.put(exchangePair, symbol);
            }
        }

        //查询币对信息
        try {
            for (RspSymbol symbol : querySymbols(new Symbol())) {
                for (Map.Entry<String, ArbitrageSymbol> entry : arbitrageSymbolMap.entrySet()) {
                    if (symbol.getSymbol().getBaseCurrency().equals(baseCurrency) && symbol.getSymbol().getQuoteCurrency().equals(quoteCurrency)) {
                        if (symbol.getSymbol().getExchangeType().equals(entry.getValue().ExchangeA)) {
                            Precision precision = new Precision();
                            precision.exchange = entry.getValue().ExchangeA;
                            precision.symbol = symbol.getSymbol();
                            precision.baseCurrency = baseCurrency;
                            precision.quoteCurrency = quoteCurrency;
                            precision.pricePrecision = new BigDecimal(symbol.getPricePrecision());
                            //ETH
                            if(symbol.getSymbol().getExchangeType().equals("binance"))
                                precision.pricePrecision = new  BigDecimal("2");

                            if(precision.pricePrecision.compareTo(new BigDecimal("4")) == 1)
                            {
                                precision.pricePrecision = new BigDecimal("4");
                            }
                            precision.amountPrecision = new BigDecimal(symbol.getAmountPrecision());
                            precision.minAmount = new BigDecimal(symbol.getMinAmount());
                            entry.getValue().aSymbolPrecision = precision;
                        }
                        if (symbol.getSymbol().getExchangeType().equals(entry.getValue().ExchangeB)) {
                            Precision precision = new Precision();
                            precision.exchange = entry.getValue().ExchangeB;
                            precision.symbol = symbol.getSymbol();
                            precision.baseCurrency = baseCurrency;
                            precision.quoteCurrency = quoteCurrency;
                            precision.pricePrecision = new BigDecimal(symbol.getPricePrecision());

                            //ETH
                            if(symbol.getSymbol().getExchangeType().equals("binance"))
                                precision.pricePrecision = new  BigDecimal("2");

                            if(precision.pricePrecision.compareTo(new BigDecimal("4")) > 0)
                            {
                                precision.pricePrecision = new BigDecimal("4");
                            }

                            //precision.amountPrecision = new BigDecimal("8");
                            precision.amountPrecision = new BigDecimal(symbol.getAmountPrecision());
                            precision.minAmount = new BigDecimal(symbol.getMinAmount());

                            entry.getValue().bSymbolPrecision = precision;

                        }
                        log.debug("symbol:" + symbol.toString());
                    }
                }
            }
        } catch (StrategyException e) {
            log.warn("query symbol failed: ", e);
        }

        //初始化仓位
        getSubaccounts().values().forEach(subaccount -> {
            if(exchanges.contains(subaccount.getExchangeType())){
                updatePositionMap(subaccount, "base", baseCurrency);
                updatePositionMap(subaccount, "quote", quoteCurrency);
            }
        });

        expectedPrice.arbitrageName = arbitrageName;
        expectedPrice.expectedBuyPrice = basePrice;
        expectedPrice.expectedSellPrice = basePrice.add(openRange);
        expectedPrice.expectedStopBuyPrice = expectedPrice.expectedBuyPrice.subtract(rangeDelta);
        expectedPrice.expectedStopSellPrice = expectedPrice.expectedSellPrice.add(rangeDelta);
        printAllOrder();
        printAllPosition();
    }

    private void updatePositionMap(Subaccount subaccount, String type, String quoteCurrency) {
        positionMap.compute(subaccount.getExchangeType()+ quoteCurrency,(key, p)->{
            if(subaccount.getAssetBalanceMap()!=null && subaccount.getAssetBalanceMap().get(quoteCurrency)!=null){
                if(p==null){
                    p = new Position(subaccount.getExchangeType(), subaccount.getSubaccountCode(), type, subaccount.getAssetBalanceMap().get(quoteCurrency));
                }else{
                    p.currencyLocked = subaccount.getAssetBalanceMap().get(quoteCurrency).getLocked();
                    p.currencyFree = subaccount.getAssetBalanceMap().get(quoteCurrency).getFree();
                }
            }
            return p;
        });
    }

    private void printAllOrder() {
        Order order;
        for (Map.Entry<String, Order> entry : getOrders("").entrySet()) {
            String id = entry.getKey();
            order = entry.getValue();
            log.debug("OrderID:" + id + ":" + order.toString());
        }
    }

    private void printAllPosition() {
        for (Map.Entry<String, Position> entry : positionMap.entrySet()) {
            String id = entry.getKey();
            log.info("exchange+currency:" + id + ",type:" + entry.getValue().getType() + ",value:" + entry.getValue().toString());
        }
    }

    private boolean isFullPosition(String exchangeCompare, BigDecimal quantity) {
        boolean isFull = false;
        BigDecimal range90 = new BigDecimal("0.90");

        BigDecimal baseFree = BigDecimal.ZERO;
        BigDecimal baseLocked = BigDecimal.ZERO;
        BigDecimal quoteFree = BigDecimal.ZERO;
        BigDecimal quoteLocked = BigDecimal.ZERO;

        for (Map.Entry<String, Position> entry : positionMap.entrySet()) {
            String id = entry.getKey();

            String exchange = entry.getValue().getExchange();
            String currency = entry.getValue().getCurrency();
            String type = entry.getValue().getType();
            if (exchange.equals(exchangeCompare)) {
                if (currency.equals(baseCurrency)) {
                    baseFree = entry.getValue().getCurrencyFree();
                    baseLocked = entry.getValue().getCurrencyLocked();
                } else if (currency.equals(quoteCurrency)) {
                    quoteFree = entry.getValue().getCurrencyFree();
                    quoteLocked = entry.getValue().getCurrencyLocked();
                }
            }
            log.debug("id:" + id + ",type:" + entry.getValue().getType() + ",value:" + entry.getValue().toString());
        }

        if (baseFree.compareTo(quantity.multiply(range90)) == 1)    //baseFree在固定Qty表示交易所持多仓满仓
        {
            isFull = true;
        }

        log.debug("isFullPosition():isFull:" + isFull + ",exchange=" + exchangeCompare + ",quantity:" + quantity.toString());

        return isFull;

    }

    private PositionStatus getStatusByPosition(String exchangeCompare, BigDecimal maxTradeQuantity, BigDecimal lastPrice) {

        log.debug("getStatusByPosition():exchangeCompare:" + exchangeCompare + ",maxTradeQuantity:" + maxTradeQuantity.toString() + ",lastPrice:" + lastPrice.toString());


        BigDecimal range01 = new BigDecimal("0.01");
        BigDecimal range10 = new BigDecimal("0.10");
        BigDecimal range90 = new BigDecimal("0.90");
        BigDecimal range110 = new BigDecimal("1.10");

        BigDecimal baseFree = BigDecimal.ZERO;
        BigDecimal baseLocked = BigDecimal.ZERO;
        BigDecimal quoteFree = BigDecimal.ZERO;
        BigDecimal quoteLocked = BigDecimal.ZERO;

        for (Map.Entry<String, Position> entry : positionMap.entrySet()) {
            String id = entry.getKey();

            String exchange = entry.getValue().getExchange();
            String currency = entry.getValue().getCurrency();
            String type = entry.getValue().getType();
            if (exchange.equals(exchangeCompare)) {
                if (currency.equals(baseCurrency)) {
                    baseFree = entry.getValue().getCurrencyFree();
                    baseLocked = entry.getValue().getCurrencyLocked();
                } else if (currency.equals(quoteCurrency)) {
                    quoteFree = entry.getValue().getCurrencyFree();
                    quoteLocked = entry.getValue().getCurrencyLocked();
                }
            }
            log.debug("id:" + id + ",type:" + entry.getValue().getType() + ",value:" + entry.getValue().toString());
        }

        log.debug("exchangeCompare:" + exchangeCompare + ",maxTradeQuantity:" + maxTradeQuantity.toString());

        PositionStatus status = PositionStatus.READY;
        //根据资金信息判断当前属于哪种状态

        if (quoteLocked.compareTo(maxTradeQuantity.multiply(range10).multiply(lastPrice)) >0 )          //quoteLocked>0表示有买开单，残余仓位即为0
        {
            boolean hasOrder = false;
            for (Order order: getOrders("").values()) {
                if (order.getSymbol().getExchangeType().equals(exchangeCompare) && order.getSide().equals("buy")
                        && (order.getOrderStatus()==OrderStatus.SUBMITTED || order.getOrderStatus()==OrderStatus.PARTIALLY_FILLED)) {
                    hasOrder = true;
                    break;
                }
            }
            if (hasOrder)
                status = PositionStatus.BUYOPEN;
            else
                status = PositionStatus.NOPOSITION;

        } else if (quoteLocked.compareTo(maxTradeQuantity.multiply(range10.multiply(lastPrice))) < 0) {
            if (baseLocked.compareTo(maxTradeQuantity.multiply(range01)) > 0)   // baseLocked>0表示有卖平单,由于精度问题，残余仓位即为0
            {
                boolean hasOrder = false;
                for (Order order: getOrders("").values()) {
                    if (order.getSymbol().getExchangeType().equals(exchangeCompare) && order.getSide().equals("sell")
                            && (order.getOrderStatus()==OrderStatus.SUBMITTED || order.getOrderStatus()==OrderStatus.PARTIALLY_FILLED)) {
                        hasOrder = true;
                        break;
                    }
                }
                if (hasOrder)
                    status = PositionStatus.SELLCLOSE;
                else
                    status = PositionStatus.LONGPOSITION;
            } else if (baseLocked.compareTo(maxTradeQuantity.multiply(range01)) < 0) {
                if (baseFree.compareTo(maxTradeQuantity.multiply(range10)) > 0)   //baseFree>0&&baseLocked=0,表示多仓
                {
                    status = PositionStatus.LONGPOSITION;
                }
	           /* else if(baseFree.compareTo(quantity.multiply(range90)) == 1
	            		)    //baseFree在固定Qty表示交易所持多仓满仓
	            {
	            	status = PositionStatus.LONGFULLPOSITION;
	            }*/
                else if ((baseFree.compareTo(maxTradeQuantity.multiply(range01)) == -1
                        || (baseFree.compareTo(maxTradeQuantity.multiply(range01)) == 1 && baseFree.compareTo(maxTradeQuantity.multiply(range10)) == -1)))  //baseFree==0&&baseLocked==0,表示无仓
                {
                    status = PositionStatus.NOPOSITION;
                }
            }
        }

        return status;

        //READY,NOPOSITION,LONGPOSITION,LONGFULLPOSITION,BUYOPEN,SELLCLOSE
    }

    /**
     * 订阅行情，比较不同交易所的ask1与bid1，
     */
    private void initMarketData() {
        for (Map.Entry<String, Symbol> entry : symbols.entrySet()) {
            log.debug("订阅了" + entry.getValue().getExchangeType() + "交易所的行情");
            subDepths(entry.getValue());
        }
    }




    private String getTradeQuantity(ArbitrageSymbol arbitrageSymbol, String exchange, BigDecimal qty) {
        String tradeQ = "0";
        BigDecimal tradeQty = BigDecimal.ZERO;

        BigDecimal range10 = new BigDecimal("0.10");
        BigDecimal range99 = new BigDecimal("0.90");
        BigDecimal range101 = new BigDecimal("1.01");

        for (Map.Entry<String, ArbitrageSymbol> entry : arbitrageSymbolMap.entrySet()) {
            if (entry.getKey() != null) {
                if (arbitrageSymbol.equals(entry.getValue())) {
                    if (exchange.equals(entry.getValue().getExchangeA())) {
                        Precision aPrecision = entry.getValue().getASymbolPrecision();
                        BigDecimal amountPrecision = aPrecision.amountPrecision;
                        BigDecimal minAmount = aPrecision.minAmount;

                        BigDecimal maxTradeQty = minAmount.max(qty);
                        tradeQty = new BigDecimal(String.valueOf(maxTradeQty)).setScale(amountPrecision.intValue(), RoundingMode.FLOOR);

                        log.debug("getExchangeA=" + exchange + ",amountPrecision=" + amountPrecision.toString() + ",minAmount=" + minAmount.toString()
                                + ",tradeQty=" + tradeQty);

                    } else if (exchange.equals(entry.getValue().getExchangeB())) {
                        Precision bPrecision = entry.getValue().getBSymbolPrecision();
                        BigDecimal amountPrecision = bPrecision.amountPrecision;
                        BigDecimal minAmount = bPrecision.minAmount;

                        BigDecimal maxTradeQty = minAmount.max(qty);

                        tradeQty = new BigDecimal(String.valueOf(maxTradeQty)).setScale(amountPrecision.intValue(), RoundingMode.FLOOR);

                        log.debug("getExchangeB=" + exchange + ",amountPrecision=" + amountPrecision.toString() + ",minAmount=" + minAmount.toString()
                                + ",tradeQty=" + tradeQty);
                    }
                }
            }
        }


        if (tradeQty.compareTo(maxTradeQuantity.multiply(range10)) == -1) {
            tradeQ = "0";
        } else {
            tradeQ = tradeQty.stripTrailingZeros().toPlainString();
        }


        log.debug("getTradeQuantity():exchange=" + exchange + ",qty=" + qty.toString() + ",tradeQty=" + tradeQty);

        return tradeQty.stripTrailingZeros().toPlainString();
    }

    private String getTradePrice(ArbitrageSymbol arbitrageSymbol, String exchange, String buySell) {
        BigDecimal tradePrice = BigDecimal.ZERO;
        String tradeP = "0";
        BigDecimal traderDelta = gDelta;
        for (Map.Entry<String, ArbitrageSymbol> entry : arbitrageSymbolMap.entrySet()) {
            if (entry.getKey() != null) {
                if (arbitrageSymbol.equals(entry.getValue())) {
                    if (exchange.equals(entry.getValue().getExchangeA())) {
                        Precision aPrecision = entry.getValue().getASymbolPrecision();
                        BigDecimal pricePrecision = aPrecision.pricePrecision;
                        BigDecimal delta = traderDelta.divide(new BigDecimal(10).pow(pricePrecision.intValue()));

                        if (buySell.equals("buy")) {
                            tradePrice = entry.getValue().getExchangABidAsk1().ask1Price.subtract(delta);

                        } else if (buySell.equals("sell")) {
                            tradePrice = entry.getValue().getExchangABidAsk1().bid1Price.add(delta);
                        }


                        tradePrice.setScale(pricePrecision.intValue(), RoundingMode.FLOOR);

                    } else if (exchange.equals(entry.getValue().getExchangeB())) {

                        Precision bPrecision = entry.getValue().getBSymbolPrecision();

                        BigDecimal pricePrecision = bPrecision.pricePrecision;

                        BigDecimal delta = traderDelta.divide(new BigDecimal(10).pow(pricePrecision.intValue()));

                        if (buySell.equals("buy")) {
                            tradePrice = entry.getValue().getExchangBBidAsk1().ask1Price.subtract(delta);
                            ;
                        } else if (buySell.equals("sell")) {
                            tradePrice = entry.getValue().getExchangBBidAsk1().bid1Price.add(delta);
                        }

                        tradePrice = tradePrice.setScale(pricePrecision.intValue(), RoundingMode.FLOOR);
                    }
                }
            }
        }


        tradeP = tradePrice.stripTrailingZeros().toPlainString();
        log.debug("getTradePrice():exchange=" + exchange + ",tradePrice=" + tradePrice.toString() + ",tradeP=" + tradeP);


        return tradeP;
    }

    /**
     * 处理最新的bid1 ask1的数据，决定是否要进行交易
     */
    private void runStrategy(ArbitrageSymbol arbitrageSymbol) {

        StrategyStatus oldStatus = arbitrageSymbol.status;

        if (arbitrageSymbol.getArbitrageAskPrice().compareTo(expectedPrice.expectedBuyPrice) == -1) {

            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                arbitrageSymbol.previousStatus = StrategyStatus.Buy;
            }

            arbitrageSymbol.status = StrategyStatus.Buy;
        } else if (arbitrageSymbol.getArbitrageBidPrice().compareTo(expectedPrice.expectedSellPrice) == 1) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                arbitrageSymbol.previousStatus = StrategyStatus.Sell;
            }

            arbitrageSymbol.status = StrategyStatus.Sell;
        } else if (arbitrageSymbol.getArbitrageAskPrice().compareTo(expectedPrice.expectedStopBuyPrice) == -1) {
            //arbitrageSymbol.previousStatus = arbitrageSymbol.status;
            //arbitrageSymbol.status = StrategyStatus.StopBuy;
        } else if (arbitrageSymbol.getArbitrageBidPrice().compareTo(expectedPrice.expectedSellPrice) == 1) {
            //arbitrageSymbol.previousStatus = arbitrageSymbol.status;
            //arbitrageSymbol.status = StrategyStatus.StopSell;
        } else if ((arbitrageSymbol.getArbitrageAskPrice().compareTo(expectedPrice.expectedBuyPrice) == 1
                && arbitrageSymbol.getArbitrageAskPrice().compareTo(expectedPrice.expectedSellPrice) == -1)
                || (arbitrageSymbol.getArbitrageBidPrice().compareTo(expectedPrice.expectedBuyPrice) == 1
                && arbitrageSymbol.getArbitrageBidPrice().compareTo(expectedPrice.expectedSellPrice) == -1)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                arbitrageSymbol.previousStatus = StrategyStatus.Buy;
            } else if (arbitrageSymbol.status.equals(StrategyStatus.NoN)) {
                //arbitrageSymbol.previousStatus = StrategyStatus.NoN;
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                arbitrageSymbol.previousStatus = StrategyStatus.Sell;
            }

            if (arbitrageSymbol.previousStatus.equals(StrategyStatus.Buy) || arbitrageSymbol.previousStatus.equals(StrategyStatus.Sell)) {
                //不更新previous
            }

            arbitrageSymbol.status = StrategyStatus.NoN;
        }

        PositionStatus statusA = getStatusByPosition(exchanges.get(0), maxTradeQuantity, arbitrageSymbol.exchangABidAsk1.ask1Price);
        PositionStatus statusB = getStatusByPosition(exchanges.get(1), maxTradeQuantity, arbitrageSymbol.exchangBBidAsk1.ask1Price);
        log.debug("StrategyStatus():" + arbitrageSymbol.status + ",statusA=" + statusA + ",statusB=" + statusB);
        log.debug("Data():B,A=" + arbitrageSymbol.getArbitrageBidPrice() + "," + arbitrageSymbol.getArbitrageAskPrice() + ",Expected,B,A=" + expectedPrice.expectedBuyPrice.toString() + "," + expectedPrice.expectedSellPrice.toString());


        if (!oldStatus.equals(arbitrageSymbol.status))
        {
            log.warn("After changed:" + arbitrageSymbol.toString() + "  " + expectedPrice.toString());
            log.warn("StrategyStatus():" + arbitrageSymbol.status + ",statusA=" + statusA + ",statusB=" + statusB);
            log.warn("Data():B,A=" + arbitrageSymbol.getArbitrageBidPrice() + "," + arbitrageSymbol.getArbitrageAskPrice() + ",Expected,B,A=" + expectedPrice.expectedBuyPrice.toString() + "," + expectedPrice.expectedSellPrice.toString());

        }

        if (statusA.equals(PositionStatus.NOPOSITION) && statusB.equals(PositionStatus.NOPOSITION)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                //Buy A
                String ABuyPrice = getTradePrice(arbitrageSymbol, exchanges.get(0), "buy");
                BigDecimal AFreeQty = positionMap.get(exchanges.get(0) + baseCurrency).getCurrencyFree();
                String expectedBuyQty = getTradeQuantity(arbitrageSymbol, exchanges.get(0), maxTradeQuantity.subtract(AFreeQty));
                if (canSubmitOrder(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "buy", arbitrageSymbol.getUpdateTime())) {
                    this.buySell(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "buy", ABuyPrice, expectedBuyQty);
                }
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                //Buy B
                String bBuyPrice = getTradePrice(arbitrageSymbol, exchanges.get(1), "buy");
                BigDecimal BFreeQty = positionMap.get(exchanges.get(1) + baseCurrency).getCurrencyFree();
                String expectedBuyQty = getTradeQuantity(arbitrageSymbol, exchanges.get(1), maxTradeQuantity.subtract(BFreeQty));
                if (canSubmitOrder(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "buy", arbitrageSymbol.getUpdateTime())) {
                    this.buySell(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "buy", bBuyPrice, expectedBuyQty);
                }
            }
        } else if (statusA.equals(PositionStatus.LONGPOSITION) && statusB.equals(PositionStatus.NOPOSITION)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                if (!isFullPosition(exchanges.get(0), maxTradeQuantity)) {
                    //Buy A
                    String ABuyPrice = getTradePrice(arbitrageSymbol, exchanges.get(0), "buy");
                    BigDecimal AFreeQty = positionMap.get(exchanges.get(0) + baseCurrency).getCurrencyFree();
                    String expectedBuyQty = getTradeQuantity(arbitrageSymbol, exchanges.get(0), maxTradeQuantity.subtract(AFreeQty));
                    if (canSubmitOrder(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "buy", arbitrageSymbol.getUpdateTime())) {
                        this.buySell(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "buy", ABuyPrice, expectedBuyQty);
                    }
                }
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                //Sell A
                String ASellPrice = getTradePrice(arbitrageSymbol, exchanges.get(0), "sell");
                BigDecimal AFreeQty = positionMap.get(exchanges.get(0) + baseCurrency).getCurrencyFree();
                String expectedSellQty = getTradeQuantity(arbitrageSymbol, exchanges.get(0), AFreeQty);
                if (canSubmitOrder(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "sell", arbitrageSymbol.getUpdateTime())) {
                    this.buySell(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "sell", ASellPrice, expectedSellQty);
                }
            }
        } else if (statusA.equals(PositionStatus.LONGPOSITION) && statusB.equals(PositionStatus.LONGPOSITION)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                //Sell B
                String BSellPrice = getTradePrice(arbitrageSymbol, exchanges.get(1), "sell");
                BigDecimal BFreeQty = positionMap.get(exchanges.get(1) + baseCurrency).getCurrencyFree();

                String expectedSellQty = getTradeQuantity(arbitrageSymbol, exchanges.get(1), BFreeQty);

                if (canSubmitOrder(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "sell", arbitrageSymbol.getUpdateTime())) {
                    this.buySell(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "sell", BSellPrice, expectedSellQty);
                }
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                //Sell A
                String ASellPrice = getTradePrice(arbitrageSymbol, exchanges.get(0), "sell");
                BigDecimal AFreeQty = positionMap.get(exchanges.get(0) + baseCurrency).getCurrencyFree();
                String expectedSellQty = getTradeQuantity(arbitrageSymbol, exchanges.get(0), AFreeQty);
                if (canSubmitOrder(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "sell", arbitrageSymbol.getUpdateTime())) {
                    this.buySell(exchanges.get(0), positionMap.get(exchanges.get(0) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(0)), "sell", ASellPrice, expectedSellQty);
                }
            }
        } else if (statusA.equals(PositionStatus.NOPOSITION) && statusB.equals(PositionStatus.LONGPOSITION)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                //Sell B
                String BSellPrice = getTradePrice(arbitrageSymbol, exchanges.get(1), "sell");
                BigDecimal BFreeQty = positionMap.get(exchanges.get(1) + baseCurrency).getCurrencyFree();

                String expectedSellQty = getTradeQuantity(arbitrageSymbol, exchanges.get(1), BFreeQty);

                if (canSubmitOrder(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "sell", arbitrageSymbol.getUpdateTime())) {
                    this.buySell(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "sell", BSellPrice, expectedSellQty);
                }
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                if (!isFullPosition(exchanges.get(1), maxTradeQuantity)) {
                    //Buy B
                    String bBuyPrice = getTradePrice(arbitrageSymbol, exchanges.get(1), "buy");
                    BigDecimal BFreeQty = positionMap.get(exchanges.get(1) + baseCurrency).getCurrencyFree();
                    String expectedBuyQty = getTradeQuantity(arbitrageSymbol, exchanges.get(1), maxTradeQuantity.subtract(BFreeQty));
                    if (canSubmitOrder(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "buy", arbitrageSymbol.getUpdateTime())) {
                        this.buySell(exchanges.get(1), positionMap.get(exchanges.get(1) + baseCurrency).getSubaccount(), symbols.get(exchanges.get(1)), "buy", bBuyPrice, expectedBuyQty);
                    }
                }
            }
        } else if (statusA.equals(PositionStatus.BUYOPEN) && statusB.equals(PositionStatus.NOPOSITION)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                cancelAllOrderByCondition(arbitrageSymbol, exchanges.get(0), "buy", arbitrageSymbol.getUpdateTime());
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                cancelAllOrder(symbols.get(exchanges.get(0)), "buy");
            } else {
                cancelAllOrder(symbols.get(exchanges.get(0)), "buy");
            }
        } else if (statusA.equals(PositionStatus.NOPOSITION) && statusB.equals(PositionStatus.BUYOPEN)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                cancelAllOrder(symbols.get(exchanges.get(1)), "buy");
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                cancelAllOrderByCondition(arbitrageSymbol, exchanges.get(1), "buy", arbitrageSymbol.getUpdateTime());
            } else {
                cancelAllOrder(symbols.get(exchanges.get(1)), "buy");
            }
        } else if (statusA.equals(PositionStatus.SELLCLOSE) && statusB.equals(PositionStatus.NOPOSITION)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                cancelAllOrder(symbols.get(exchanges.get(0)), "sell");
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                cancelAllOrderByCondition(arbitrageSymbol, exchanges.get(0), "sell", arbitrageSymbol.getUpdateTime());
            } else {
                cancelAllOrder(symbols.get(exchanges.get(0)), "sell");
            }
        } else if (statusA.equals(PositionStatus.NOPOSITION) && statusB.equals(PositionStatus.SELLCLOSE)) {
            if (arbitrageSymbol.status.equals(StrategyStatus.Buy)) {
                cancelAllOrderByCondition(arbitrageSymbol, exchanges.get(1), "sell", arbitrageSymbol.getUpdateTime());
            } else if (arbitrageSymbol.status.equals(StrategyStatus.Sell)) {
                cancelAllOrder(symbols.get(exchanges.get(1)), "sell");
            } else {
                cancelAllOrder(symbols.get(exchanges.get(1)), "sell");
            }
        } else {
            log.debug("未知状态:" + arbitrageSymbol.status + ",statusA=" + statusA + ",statusB=" + statusB);
        }
    }

    private void printAllArbitrageHD() {
        for (Map.Entry<String, ArbitrageSymbol> entry : arbitrageSymbolMap.entrySet()) {
            if (entry.getValue() != null) {
                ArbitrageSymbol arbitrageSymbol = entry.getValue();
                log.debug(arbitrageSymbol.toString());
                // log.debug(entry.getValue());
            }
        }
    }

    private boolean cancelAllOrderByCondition(ArbitrageSymbol arbitrageSymbol, String exchange, String buySell, long updateTime) {
        boolean flag = false;

        for (Map.Entry<String, Order> entry : getOrders("").entrySet()) {

            String orderId = entry.getKey();
            Order order = entry.getValue();
            long insertTime = order.getExchangeInsertTime();
            BigDecimal insertPrice = order.getPrice();
            String expectedTradePrice = this.getTradePrice(arbitrageSymbol, exchange, buySell);

            if (order.getSymbol().getExchangeType().equals(exchange) &&  (order.getOrderStatus() == OrderStatus.SUBMITTED || order.getOrderStatus() == OrderStatus.PARTIALLY_FILLED)

                    && updateTime > insertTime + 5000) {
                if (order.getSide().equals("buy") && (insertPrice.compareTo(new BigDecimal(expectedTradePrice)) < 0)) {
                    flag = true;
                } else if (order.getSide().equals("sell") && (insertPrice.compareTo(new BigDecimal(expectedTradePrice)) > 0)) {
                    flag = true;

                }
                if (flag) {
                    try{
                        this.cancelOrder(orderId);
                    }catch (StrategyException e){
                        log.warn("cancel order failed: ", e);
                    }
                    log.debug("cancelAllOrderByCondition() orderId=" + orderId + ",exchange=" + exchange + ",buySell=" + buySell + ",updateTime=" + updateTime + ",tradeP=" + expectedTradePrice);
                }
            }
        }

        return flag;
    }

    private boolean canSubmitOrder(String exchange, String subaccountCode, Symbol symbol, String buySell, long updateTime) {
        boolean flag = false, hasSubmittedOrder = false;

        long lastInsertTime = 0;
        for (Map.Entry<String, Order> entry : getOrders(subaccountCode).entrySet()) {
            Order order = entry.getValue();
            if (order.getSymbol().getExchangeType().equals(exchange) && order.getSide().equals(buySell)) {
                if (order.getOrderStatus().equals(OrderStatus.SUBMITTED)) {
                    hasSubmittedOrder = true;
                    break;
                }

            }
        }

        if (!hasSubmittedOrder) {
            for (Map.Entry<String, Order> entry : getOrders(subaccountCode).entrySet()) {
                Order order = entry.getValue();
                if (order.getSide().equals(buySell) && order.getSymbol().getExchangeType().equals(exchange)) {
                    long insertTime = order.getExchangeInsertTime();
                    if (insertTime > lastInsertTime) {
                        lastInsertTime = insertTime;
                    }
                }
            }
            if (updateTime > lastInsertTime + 3000)
                flag = true;
        } else {
            flag = false;
        }

        log.debug("canSubmitOrder() flag=" + flag + ",exchange=" + exchange + ",hasOrder=" + hasSubmittedOrder + ",buySell=" + buySell + ",updateTime=" + updateTime + ",lastInsertTime=" + lastInsertTime);

        return flag;

    }

    /**
     * 调用接口进行限价买入卖出，收到响应后将orderId放入orderMap中
     */
    private void buySell(String exchange, String subaccountCode, Symbol symbol, String buySell, String price, String quantity) {
            log.info("before insertOrder():exchange=" + symbol.getExchangeType() + ",buysell=" + buySell + ",price=" + price + ",qty=" + quantity);
            try{
                insertOrder(ReqInsertOrder.builder().subaccountCode(subaccountCode).symbol(symbol).quantity(new BigDecimal(quantity)).price(new BigDecimal(price)).side(buySell).build());
                positionMap.get(symbol.getExchangeType()+symbol.getBaseCurrency()).currencyFree = getSubaccountBalance(subaccountCode, symbol.getBaseCurrency()).getFree();
                positionMap.get(symbol.getExchangeType()+symbol.getBaseCurrency()).currencyLocked = getSubaccountBalance(subaccountCode, symbol.getBaseCurrency()).getLocked();
                positionMap.get(symbol.getExchangeType()+symbol.getQuoteCurrency()).currencyFree = getSubaccountBalance(subaccountCode, symbol.getQuoteCurrency()).getFree();
                positionMap.get(symbol.getExchangeType()+symbol.getQuoteCurrency()).currencyLocked = getSubaccountBalance(subaccountCode, symbol.getQuoteCurrency()).getLocked();
            } catch (StrategyException e){
                log.warn("insert order failed", e);
            }
    }

    private void cancelAllOrder(Symbol symbol, String buySell) {
        for (Map.Entry<String, Order> entry : getOrders("").entrySet()) {
            String id = entry.getKey();
            Order order = entry.getValue();
            if (order.getOrderStatus().equals(OrderStatus.SUBMITTED) || order.getOrderStatus().equals(OrderStatus.PARTIALLY_FILLED))
            {
                if (order.getSymbol().equals(symbol) && order.getSide().equals(buySell)) {
                    try{
                        cancelOrder(order.getOrderId());
                    }catch (StrategyException e){
                        log.warn("cancel order failed: ", e);
                    }
                    log.debug("canceled: " + id);
                }
            }
        }

    }
}

